#pragma once

#ifndef BETWEEN_FACE__NO_YIELD
    #include <Arduino.h>
#endif

#include <cstdint>

#include "between_face_include_config.hpp"


namespace beef {

    struct CallbackMessage {
        uint32_t type;   // 消息类型
        uint32_t id;     // 消息ID
        uint32_t value;  // 消息值
        void *ptr;       // 附加数据指针
    };


    class InterfaceBase {
       private:
        bool _active;

       protected:
        // 等待指定时间
        void wait_for(TimeType t) {
            TimeCycle tc;

            while (tc.wait(t)) {
                tick();
            }
        }


        // 等待，直到条件为true
        void wait_for(bool (*is_true)()) {
            while (!is_true()) {
                tick();
            }
        }


        /**
         * @brief 一边等待条件变为true，一边继续执行延时任务
         *
         * 用法：while(wait_until(should_exit == true));
         *
         * @param is_true
         * @return true
         * @return false
         */
        bool wait_until(bool is_true) {
            tick();
            return !is_true;
        }


        // 界面中必须实时处理的交互操作，为了方便，不放进延时任务里
        virtual void poll() {}


        /**
         * @brief 执行界面逻辑
         *
         * 返回值可用于切换同级的其他界面
         *
         * @return int
         */
        virtual int show() = 0;


        /**
         * @brief 界面激活后只执行一次的初始化操作
         *
         * 如果sub != nullptr，表示控制权正在从这个次级界面返回，否则表示界面是从顶层激活的。
         *
         * init 运行时，active 返回false，poll 不会执行，所以可以在poll 中调用init，不会导致init 重入。
         *
         * @param sub
         */
        virtual void init(InterfaceBase *sub) = 0;


        /**
         * @brief 用来接收各种回调的函数，主要是菜单输入回调
         *
         * @param value
         * @param type
         * @param ptr
         * @return int
         */
        virtual int callback(CallbackMessage &msg) {
            // 默认不处理输入
            return 0;
        }


        /**
         * @brief 将控制权转移给次级界面
         *
         * 切换到次级界面后还要处理返回时的逻辑，和界面被激活时一样，会先调用init。
         *
         * 主动切换到次级界面的操作应该发生在show 函数内，所以返回就回到了show 函数里，
         * 此时应该做两件事：1. 恢复界面状态；2. 处理次级界面的执行结果。
         *
         * 要避免重入show 函数，所以恢复界面的工作只能放在init 里完成。
         *
         * @param sub
         */
        int enter(InterfaceBase *sub) {
            _active = false;
            int r = sub->enter();
            init(sub);
            _active = true;
            return r;
        }


        int enter(InterfaceBase &sub) {
            return enter(&sub);
        }

       public:
        InterfaceBase() {}

        /**
         * @brief 激活界面，执行界面逻辑
         *
         * 界面被激活时，先执行init 过程，再调用show 进入界面loop
         *
         */
        int enter() {
            init(nullptr);
            _active = true;
            int r = show();
            _active = false;
            return r;
        }

        bool active() const {
            return _active;
        }

        void tick() {
#ifndef BETWEEN_FACE__NO_YIELD
            yield();
#endif
            if (_active) {
                poll();
            }
        }
    };

}  // namespace beef